package dipl.algorithm.math.utility;

import dipl.algorithm.exception.ArgumentException;
import dipl.algorithm.exception.ArgumentNullException;
import dipl.algorithm.math.curve.BezierCurve;
import dipl.algorithm.math.primitive.Point2d;
import dipl.algorithm.math.fp.primitive.Point2df;
import dipl.algorithm.math.primitive.Tuple2;
import dipl.algorithm.utility.convert.ApfloatConversion;
import java.util.Random;
import java.util.Vector;

/**
 * Provides methods for generation of Bezier-curves
 */
public class BezierCurveFactory {

	//
	// PUBLIC METHODS
	//

	/**
	 * Generates a bezier curve representing a polynomial in bernstein-basis
	 * with given coefficients.
	 * @param coefficients
	 * @param xFirst x-coordinate of first control point generated
	 * @param xLast x-coordinate of last control point generated
	 * @return curve
	 */
	public static BezierCurve CreateBernsteinPolynomial( double[] coefficients, double xFirst, double xLast )
		throws ArgumentNullException, ArgumentException {
		if( coefficients == null )
			throw new ArgumentNullException();
		if( coefficients.length == 0 )
			throw new ArgumentException();
		if( xLast < xFirst )
			throw new ArgumentException( "xLast < xFirst not allowed!" );

		int n = coefficients.length;
		Point2df[] controls = new Point2df[n];
		double t = xFirst;
		double incr = (xLast-xFirst)/(double)(n-1);
		for( int i = 0; i < n; i++ ) {
			controls[i] = new Point2df( t, coefficients[i] );
			t += incr;
		}
		return new BezierCurve( ApfloatConversion.ConvertPoints( controls ) );
	}

	/**
	 * Generates a bezier curve representing a polynomial in bernstein-basis
	 * with given integer coefficients.
	 * @param coefficients
	 * @param xStart start of x-Coordinate
	 * @return curve
	 */
	public static BezierCurve CreateBernsteinPolynomialIC( int[] coefficients, int xStart )
		throws ArgumentNullException, ArgumentException {
		if( coefficients == null )
			throw new ArgumentNullException();
		if( coefficients.length == 0 )
			throw new ArgumentException();

		int n = coefficients.length;
		Point2df[] controls = new Point2df[n];
		double t = (double)xStart;
		for( int i = 0; i < n; i++ ) {
			controls[i] = new Point2df( t, (double)coefficients[i] );
			t += 1.0;
		}
		return new BezierCurve( ApfloatConversion.ConvertPoints( controls ) );
	}

	/**
	 * Creates a random beziér curve
	 * @param rand
	 * @param degree
	 * @return
	 */
	public static BezierCurve CreateRandom( Random rand, int degree ) {
		return CreateRandom( rand, degree, degree, -1.0, -1.0, 1.0, 1.0 );
	}

	/**
	 * Creates a random beziér curve
	 * @param rand
	 * @param minDegree
	 * @param maxDegree
	 * @return
	 */
	public static BezierCurve CreateRandom( Random rand, int minDegree, int maxDegree ) {
		return CreateRandom( rand, minDegree, maxDegree, -1.0, -1.0, 1.0, 1.0 );
	}

	/**
	 * Creates a random beziér curve
	 * @param rand
	 * @param minDegree
	 * @param maxDegree
	 * @param minX
	 * @param minY
	 * @param maxX
	 * @param maxY
	 * @return
	 */
	public static BezierCurve CreateRandom( Random rand, int minDegree, int maxDegree, double minX, double minY, double maxX, double maxY ) {
		double min = (double)(minDegree+1);
		double max = (double)(maxDegree+1)-min;
		Point2d[] controls = new Point2d[(int)(min+rand.nextDouble()*max)];
		for( int i = 0; i < controls.length; i++ ) {
			controls[i] = new Point2d(
				ApfloatUtils.ValueOf( minX+(maxX-minX)*rand.nextDouble() ),
				ApfloatUtils.ValueOf( minY+(maxY-minY)*rand.nextDouble() ) );
		}
		BezierCurve ret = null;
		try { ret = new BezierCurve( controls ); } catch( Exception e ) {}
		return ret;
	}

	/**
	 * Creates a random beziér curve with (L,L)-bit float coefficients
	 * @param rand
	 * @param minDegree
	 * @param maxDegree
	 * @return
	 */
	public static BezierCurve CreateRandomLBitCoeff( Random rand, int minDegree, int maxDegree, int L ) {
		double min = (double)(minDegree+1);
		double max = (double)(maxDegree+1)-min;
		Point2d[] controls = Point2dSetFactory.CreateRandomLBit( rand, L, (int)(min+rand.nextDouble()*max) );
		BezierCurve ret = null;
		try { ret = new BezierCurve( controls ); } catch( Exception e ) {}
		return ret;
	}

	/**
	 * Generates a bezier curve representing a polynomial in bernstein-basis
	 * with random coefficients.
	 * @param rand 
	 * @param xFirst x-coordinate of first control point generated
	 * @param xLast x-coordinate of last control point generated
	 * @param minDegree
	 * @param maxDegree
	 * @return curve
	 */
	public static BezierCurve CreateRandomBernsteinPolynomial( Random rand, double xFirst, double xLast, int minDegree, int maxDegree )
		throws ArgumentNullException, ArgumentException {
		double min = (double)(minDegree+1);
		double max = (double)(maxDegree+1)-min;
		double[] coefficients = new double[(int)(min+rand.nextDouble()*max)];
		int n = coefficients.length;
		for( int i = 0; i < n; i++ ) {
			coefficients[i] = -1.0+rand.nextDouble()*2.0;
		}
		return CreateBernsteinPolynomial( coefficients, xFirst, xLast );
	}

 	/**
	 * Generates a bezier curve representing a polynomial in bernstein-basis
	 * with random integer coefficients.
	 * @param rand
	 * @param degree desired degree
	 * @param startX start of x-coordinate
	 * @param minCoeff lower bound of coefficient range
	 * @param maxCoeff upper bound of coefficient range
	 * @return curve
	 */
	public static BezierCurve CreateRandomBernsteinPolynomialIC(
        Random rand,
        int degree,
        int startX,
        int minCoeff,
        int maxCoeff ) throws ArgumentNullException, ArgumentException
  {
		int[] coefficients = new int[degree+1];
		int n = coefficients.length;
		for( int i = 0; i < n; i++ ) {
			coefficients[i] = minCoeff+rand.nextInt( (maxCoeff-minCoeff)+1 );
		}
		return CreateBernsteinPolynomialIC( coefficients, startX );
	}

	/**
	 * Generates a bezier curve representing a polynomial in bernstein-basis.
	 * with random integer coefficients in parameter interval [0.0,1.0]
	 * @param rand
	 * @param degree
	 * @return polynom
	 */
	public static BezierCurve CreateRandomBernsteinPolynomial_01( Random rand, int degree )
		throws ArgumentNullException, ArgumentException {
		return CreateRandomBernsteinPolynomial( rand, 0.0, 1.0, degree, degree );
	}

	/**
	 * Generates straight line curve (all control points are collinear, if minDegree > 1)
	 * @param rand
	 * @param minDegree
	 * @param maxDegree
	 * @param startX
	 * @param startY
	 * @return
	 */
	public static BezierCurve CreateStraightLine( Random rand, int minDegree, int maxDegree, double startX, double startY ) {
		double mx, my; // slopes
		Point2d[] controls;
		double min = minDegree+1;
		double max = (maxDegree+1)-(minDegree+1);
		controls = new Point2d[(int)(min+rand.nextDouble()*max)];
		double x = startX+rand.nextDouble()*2.0;
		double y = startY+rand.nextDouble()*2.0;
		controls[0] = new Point2d( ApfloatUtils.ValueOf( x ), ApfloatUtils.ValueOf( y ) );
		mx = rand.nextDouble() <= 0.5 ? -rand.nextDouble()*0.5 : rand.nextDouble()*0.5;
		my = rand.nextDouble() <= 0.5 ? -rand.nextDouble()*0.5 : rand.nextDouble()*0.5;
		for( int i = 1; i < controls.length; i++ ) {
			controls[i] = new Point2d( ApfloatUtils.ValueOf( x+mx*(double)(i) ), ApfloatUtils.ValueOf( y+my*(double)(i) ) );
		}
		BezierCurve ret = null;
		try {
			ret = new BezierCurve( controls );
		} catch( Exception e ) {
			e.printStackTrace();
		}
		return ret;
	}

	/**
	 * Generates a bezier curve representing a segment of the x-axis
	 * @param l lower bound of segment
	 * @param u upper bound of segment
	 * @param degree degree of segment (>=1)
	 * @return curve
	 */
	public static BezierCurve CreateXAxisSegment( double l, double u, int degree ) throws ArgumentException {
		if( degree < 1 )
			throw new ArgumentException();
		double[] coefficients = new double[degree+1];
		for( int i = 0; i <= degree; i++ ) {
			coefficients[i] = 0.0;
		}
		BezierCurve res = null;
		try {
			res = CreateBernsteinPolynomial( coefficients, l, u );
		}
		catch( Exception e ) {
			e.printStackTrace();
		}
		return res;
	}

	public static Vector<Tuple2<BezierCurve,BezierCurve>> GetEasyExamples() {
		if( easyExampleCurves == null ) {
			try {
				int cnt = easyExampleStrings_f.length;
				easyExampleCurves = new Vector<Tuple2<BezierCurve, BezierCurve>>( cnt, cnt );
				for( int i = 0; i < cnt; i++ ) {
					easyExampleCurves.add(
						new Tuple2<BezierCurve, BezierCurve>(
							new BezierCurve( Point2dSetFactory.CreateFromString( easyExampleStrings_f[i] ) ),
							new BezierCurve( Point2dSetFactory.CreateFromString( easyExampleStrings_g[i] ) )
						)
					);
				}
			}
			catch( Exception e ) {
				e.printStackTrace();
				return null;
			}
		}
		return easyExampleCurves;
	}

	//
	// MEMBERS
	//

	protected static String[] easyExampleStrings_f = new String[] {
		"{(-0.125,1.5);(3.625,3.25);(-14.0,4.5);(-10.0,-2.75);(13.0,-1.125);(-1.875,-5.0);(-18.0,-1.75)}",
		"{(0.5625,1.4375);(0.5625,0.0);(1.5625,0.8125);(0.0,0.875);(-26.0,-0.6875);(-10.0,8.0);(-13.0,15.0)}",
		"{(-1.0,-11.0);(7.0,0.0);(11.0,-21.0);(0.5,0.4375);(0.0,2.125);(-1.5625,-1.0);(-0.625,2.75)}",
		"{(-13.5,-13.0);(-1.75,1.875);(3.125,1.0);(0.8125,28.0)}",
		"{(-2.0,-0.875);(-8.0,7.25);(6.5,14.0);(-9.0,3.0);(0.0,-0.25)}",
		"{(5.0,-0.625);(-0.375,-1.5);(-0.875,0.875);(-4.0,-6.5);(-0.875,-1.5);(29.0,-0.25)}",
		"{(-30.0,-10.0);(-1.6875,20.0);(-0.5,-1.5);(8.0,5.75);(-7.0,-0.375)}",
		"{(0.125,5.0);(-7.0,-1.25);(0.25,10.5);(-3.25,-2.0)}",
		"{(-1.75,22.0);(0.5,0.0);(-30.0,-2.375);(21.0,1.25);(-14.5,3.0)}",
		"{(-7.0,2.25);(-6.25,-25.0);(6.0,-0.4375);(0.75,-5.75);(-1.0,2.0);(0.0,-9.0)}",
		"{(5.0,12.0);(-9.0,-29.0);(-1.75,-8.0);(-0.125,2.0);(30.0,-2.75);(0.5,-25.0)}",
		"{(-3.0,2.75);(-4.75,-18.0);(6.5,13.0);(0.5,1.875);(-8.5,19.0);(1.375,-13.0);(3.5,-1.125);(9.0,24.0)}",
		"{(-1.5625,0.25);(13.5,29.0);(-24.0,-7.25);(-3.5,1.625);(-0.375,1.625)}",
		"{(1.75,0.875);(-2.625,-5.25);(-3.25,6.0);(0.0,-0.625);(-10.0,-2.0);(0.875,-6.5);(-2.75,-0.75);(0.5,-5.5)}",
		"{(-5.5,-0.75);(-0.75,6.75);(26.0,-3.125);(-3.5,28.0);(2.625,0.75)}",
		"{(1.0625,3.0);(7.25,28.0);(10.5,-1.4375);(-2.0,1.0625);(-7.0,-11.0);(-14.0,-1.0);(-4.25,0.1875)}",
		"{(15.0,-1.875);(9.5,24.0);(0.5,-7.25);(-1.5,0.0);(-0.1875,1.375);(12.0,-4.5);(22.0,0.25)}",
		"{(24.0,1.125);(-6.5,-1.25);(-3.75,-1.25);(-1.375,-0.5625);(0.25,-4.0);(-9.0,1.25);(-2.25,-2.25)}",
		"{(-0.5,-2.0);(-2.625,-30.0);(6.75,26.0);(-2.5,-1.75);(-18.0,5.5);(1.25,-14.0)}",
		"{(-0.125,28.0);(-21.0,0.875);(-6.5,5.25);(6.75,0.75);(27.0,-8.0);(7.5,2.875);(6.75,-6.5)}",
		"{(-8.0,0.375);(13.0,2.125);(-3.5,-17.0);(-9.0,-10.0);(-1.875,-1.1875)}",
		"{(-4.75,0.4375);(-21.0,1.125);(0.25,-5.25);(3.5,-10.5);(-0.875,0.625);(-6.25,8.0);(-2.25,2.0);(-9.0,-16.0)}",
		"{(5.5,0.5);(-1.875,-17.0);(-0.5,5.0);(29.0,7.0);(20.0,5.5);(1.0,12.5)}",
		"{(-1.4375,2.25);(-1.75,-5.25);(21.0,-9.5);(-3.0,-4.0)}",
		"{(-0.25,12.0);(2.875,-1.8125);(-2.5,-21.0);(-7.25,-17.0);(0.0,3.5)}",
		"{(-0.375,0.375);(-2.0,3.375);(27.0,1.0);(-3.0,-3.25);(-6.0,-1.8125);(-9.5,-3.75);(8.0,-0.875);(-6.5,0.0)}",
		"{(3.125,-10.0);(10.5,-1.0);(0.0,1.0);(-6.5,3.625);(-0.375,0.25);(-7.25,5.75);(1.125,12.0)}",
		"{(-4.75,6.0);(-1.75,-11.5);(-11.5,2.875);(-4.0,-1.6875);(14.5,1.5625);(-0.25,0.75)}",
		"{(-29.0,-3.0);(-10.5,5.25);(-3.25,2.75);(0.0,-6.75);(-1.0,4.0);(17.0,-30.0);(1.5,13.0);(-15.0,-14.0)}",
		"{(-1.0,-29.0);(3.0,0.8125);(0.0,-22.0);(-1.625,-4.25);(0.5625,-6.5);(-9.0,0.375);(-0.125,26.0)}",
	};

	protected static String[] easyExampleStrings_g = new String[] {
		"{(0.1875,2.375);(-5.0,14.5);(-11.0,-4.0);(0.125,5.0);(-4.5,-1.125)}",
		"{(7.5,-0.75);(-15.0,7.25);(2.25,-5.5);(1.75,-3.125);(2.25,-1.75);(-17.0,0.8125);(0.0,2.75);(22.0,-0.875)}",
		"{(4.0,-8.5);(-2.625,20.0);(2.75,5.0);(3.0,0.75);(-1.0,0.25)}",
		"{(-23.0,-12.0);(-1.0,1.0625);(30.0,14.5);(-1.1875,25.0);(-0.625,6.5);(-14.0,3.375);(-17.0,2.0);(14.0,30.0)}",
		"{(-0.4375,-14.0);(6.5,3.5);(0.0,-0.5);(-5.0,-5.0);(-2.125,1.875);(-5.0,-11.0);(0.25,16.0)}",
		"{(-1.0,12.0);(-0.6875,-3.25);(-2.375,-6.0);(5.75,-1.0);(0.5,-4.0);(10.0,-29.0);(13.0,-14.0);(0.0,1.25)}",
		"{(-2.25,0.5625);(-12.0,10.0);(-12.0,-12.0);(-1.375,0.75);(5.25,3.5);(-0.25,1.25);(1.75,0.3125);(-3.0,-7.0)}",
		"{(-3.5,0.5);(10.0,5.75);(-6.5,-13.0);(-1.8125,-1.5625);(-4.75,14.0);(-1.0,-2.875);(1.6875,-3.0);(14.0,-0.3125)}",
		"{(1.0,-10.0);(-0.25,1.3125);(-17.0,0.5);(1.75,1.625)}",
		"{(-1.125,-3.0);(-5.75,6.75);(15.0,-0.6875);(-1.25,-3.5);(8.0,-1.5);(0.0,14.0);(-1.3125,-0.625);(7.0,1.1875)}",
		"{(1.25,-0.5);(0.875,-5.0);(-2.5,-1.8125);(19.0,-5.0);(-6.5,5.5)}",
		"{(1.75,1.5625);(-1.5,10.0);(2.125,5.5);(-0.5,0.3125);(-0.5,-12.0)}",
		"{(-11.0,-0.75);(-26.0,1.0);(2.0,1.375);(3.5,4.5);(7.0,20.0)}",
		"{(-6.5,-0.875);(14.0,-26.0);(-3.25,5.75);(1.375,-1.5);(-11.0,8.0);(9.0,4.5);(0.0,-7.5);(1.0625,7.0)}",
		"{(-1.0625,0.0);(4.75,2.5);(4.0,-2.25);(2.5,2.75);(0.875,3.5)}",
		"{(-17.0,3.75);(0.75,1.0);(-0.0625,-4.0);(-3.375,0.0);(13.0,-2.25);(-4.0,30.0);(-8.5,11.5)}",
		"{(2.0,2.0);(-2.25,0.0);(-0.75,-1.0625);(-0.375,-13.0);(-18.0,14.5);(1.0,-24.0)}",
		"{(2.75,-10.0);(-1.375,3.0);(0.0,-1.8125);(-2.75,-0.875);(-1.125,4.0);(-1.375,14.5);(-2.5,9.0)}",
		"{(11.0,14.0);(-1.0,4.0);(-6.0,-7.5);(-0.25,5.5);(4.0,3.5)}",
		"{(5.0,3.25);(0.75,-9.0);(2.625,1.125);(0.875,-8.0);(-11.5,3.0);(-7.0,-1.375);(12.0,0.875)}",
		"{(1.0625,-1.75);(-1.75,0.5);(3.75,0.6875);(-2.0,-25.0);(2.75,-5.5);(12.5,-0.75)}",
		"{(-0.8125,-26.0);(-8.5,-14.0);(-1.5,0.875);(-4.5,8.0);(-0.9375,6.25);(-2.25,3.5)}",
		"{(23.0,-8.0);(2.75,1.25);(-7.0,-3.0);(-15.0,0.0);(2.75,-0.9375);(3.625,8.0)}",
		"{(0.75,-10.0);(-6.0,5.5);(22.0,0.0);(-0.125,0.0);(-9.5,-1.0);(2.875,0.75);(2.5,4.25)}",
		"{(-0.25,-15.0);(0.0,27.0);(1.0,-0.1875);(29.0,-2.625);(1.5625,-13.0);(-0.5,3.75);(3.75,-6.25);(3.0,-12.0)}",
		"{(-1.75,0.1875);(4.0,2.5);(3.5,-3.0);(10.0,2.0);(2.5,3.5);(1.625,2.5)}",
		"{(25.0,21.0);(26.0,-0.8125);(1.4375,7.0);(2.5,-3.5)}",
		"{(3.0,-5.0);(0.0,0.3125);(14.0,-7.0);(-12.5,25.0);(-0.6875,3.5);(1.4375,-0.375)}",
		"{(-0.625,-29.0);(-0.5,-13.0);(6.75,0.1875);(-3.75,0.5);(-5.5,0.5625);(23.0,13.5)}",
		"{(-10.0,-0.5);(1.5,7.5);(-0.125,1.1875);(-0.25,0.4375);(0.0,-1.0);(-0.875,18.0);(7.5,-0.9375);(-3.125,-1.1875)}",
	};

	protected static Vector<Tuple2<BezierCurve,BezierCurve>> easyExampleCurves = null;
}
